﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.GrammarConstruction
{
    [Serializable]
    public sealed class GrammarProductionTable : ReadOnlyCollection<GrammarProduction>
    {
        [NonSerialized]
        private ReadOnlyCollection<GrammarProduction> _acceptProductionsWrapper;
        private readonly List<GrammarProduction> _acceptProductions;
        public ReadOnlyCollection<GrammarProduction> AcceptProductions
        {
            get
            {
                if (_acceptProductionsWrapper == null)
                    _acceptProductionsWrapper = _acceptProductions.AsReadOnly();
                return _acceptProductionsWrapper;
            }
        }

        [NonSerialized]
        private ReadOnlyCollection<GrammarProduction> _userProductionsWrapper;
        private readonly List<GrammarProduction> _userProductions;
        public ReadOnlyCollection<GrammarProduction> UserProductions
        {
            get
            {
                if (_userProductionsWrapper == null)
                    _userProductionsWrapper = _userProductions.AsReadOnly();
                return _userProductionsWrapper;
            }
        }

        [NonSerialized]
        private ReadOnlyCollection<GrammarProduction> _autoDefinedProductionsWrapper;
        private readonly List<GrammarProduction> _autoDefinedProductions;
        public ReadOnlyCollection<GrammarProduction> AutoDefinedProductions
        {
            get
            {
                if (_autoDefinedProductionsWrapper == null)
                    _autoDefinedProductionsWrapper = _autoDefinedProductions.AsReadOnly();
                return _autoDefinedProductionsWrapper;
            }
        }

        internal GrammarProductionTable()
            : base(new List<GrammarProduction>())
        {
            _acceptProductions = new List<GrammarProduction>();
            _userProductions = new List<GrammarProduction>();
            _autoDefinedProductions = new List<GrammarProduction>();
        }

        private GrammarProduction Define(GrammarNonterminalSymbol owner, SyntacticRuleDeclarator declarator, GrammarGoalDescriptor goal)
        {
            if (owner == null)
                throw new ArgumentNullException(nameof(owner));
            if (declarator == null)
                throw new ArgumentNullException(nameof(declarator));
            GrammarProduction prod = owner.DefineProduction();
            prod.Number = Items.Count;
            prod.Declarator = declarator;
            prod.Goal = goal;
            if (prod.IsAccept)
                _acceptProductions.Add(prod);
            else if (prod.IsAutoDefined)
                _autoDefinedProductions.Add(prod);
            else
                _userProductions.Add(prod);
            Items.Add(prod);
            return prod;
        }

        internal GrammarProduction Define(GrammarNonterminalSymbol owner, SyntacticExpression pattern, GrammarGoalDescriptor goal)
        {
            if (owner == null)
                throw new ArgumentNullException(nameof(owner));
            if (pattern == null)
                throw new ArgumentNullException(nameof(pattern));
            SyntacticRuleDeclarator declarator = SyntacticRuleDeclarator.New(pattern);
            return Define(owner, declarator, goal);
        }

        internal GrammarProduction Define(GrammarNonterminalSymbol owner, SyntacticRuleDeclarator declarator)
        {
            if (owner == null)
                throw new ArgumentNullException(nameof(owner));
            if (declarator == null)
                throw new ArgumentNullException(nameof(declarator));
            return Define(owner, declarator, null);
        }

        internal GrammarProduction Define(GrammarNonterminalSymbol owner, SyntacticExpression pattern)
        {
            if (owner == null)
                throw new ArgumentNullException(nameof(owner));
            if (pattern == null)
                throw new ArgumentNullException(nameof(pattern));
            return Define(owner, pattern, null);
        }

        public sealed override string ToString()
        {
            return string.Join("\n", Items);
        }
    }
}
